Хорошей практикой является оставлять свои комментарии после исправлений замечаний или для вопросов, разъяснений. Для того чтобы мне было легче их найти, то выделяй, пожалуйста, с помощью цвета, например вот так:
Вопрос, который не останется незамеченным
</div>
В рамках финального спринта у вас есть лимит на количество итераций проверок. Я буду для информативности их отмечать в итоговом ревью:
[1/6]
Итог ревью - ...
</div>
p.s.: не удаляй мои замечания, если предстоит что-то доработать в проекте.</h7>
Ссылка на презентацию: https://disk.yandex.lt/i/fstdmbKvNHIDuQ
Задача:
Проанализируйте связь целевого события — просмотра контактов — и других действий пользователей.
Оцените, какие действия чаще совершают те пользователи, которые просматривают контакты.
Проведите исследовательский анализ данных
Проанализируйте влияние событий на совершение целевого события
Проверьте статистические гипотезы
Одни пользователи совершают действия tips_show и tips_click , другие —только tips_show . Проверьте гипотезу: конверсия в просмотры контактов различается у этих двух групп.
Сформулируйте собственную статистическую гипотезу. Дополните её нулевой и альтернативной гипотезами. Проверьте гипотезу с помощью статистического теста.
Датасет содержит данные о событиях, совершенных в мобильном приложении "Ненужные вещи". В нем пользователи продают свои ненужные вещи, размещая их на доске объявлений.
В датасете содержатся данные пользователей, впервые совершивших действия в приложении после 7 октября 2019 года.
Цели исследования:
Управление вовлеченностью клиентов (адаптация приложения по целевой и смежной аудитории) будет актуально только на основе данных о поведении пользователей.
Получить на основе поведения пользователей гипотезы о том как можно было бы улучшить приложение с точки зрения пользовательского опыта.
Шаг 1. Открыть файлы с данными и изучите общую информацию
Шаг 2. Подготовка данных
Замена столбцов, приведение к нужному типу данных, просмотр дубликатов и пропусков, если требуется и удаление их.
Шаг 3. Проведите исследовательский анализ данных
Проанализируйте связь целевого события — просмотра контактов — и других действий пользователей:
Оцените, какие действия чаще совершают те пользователи, которые просматривают контакты:
Шаг 4. Проверка гипотез:
где, tips_show — увидел рекомендованные объявления, tips_click — кликнул по рекомендованному объявлению.
где, advert_open- открыл карточки объявления.
Шаг 5. Рекомендации заказчику, выводы
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
import datetime as dt
import plotly.express as px
from scipy import stats as st
import math as mth
from tqdm import tqdm
import requests
from plotly import graph_objs as go
import warnings
warnings.simplefilter("ignore")
data = pd.read_csv('https://code.s3.yandex.net/datasets/mobile_dataset.csv')
data.head(3)
| event.time | event.name | user.id | |
|---|---|---|---|
| 0 | 2019-10-07 00:00:00.431357 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 1 | 2019-10-07 00:00:01.236320 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 2 | 2019-10-07 00:00:02.245341 | tips_show | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
sources = pd.read_csv('https://code.s3.yandex.net/datasets/mobile_sources.csv')
sources.head(3)
| userId | source | |
|---|---|---|
| 0 | 020292ab-89bc-4156-9acf-68bc2783f894 | other |
| 1 | cf7eda61-9349-469f-ac27-e5b6f5ec475c | yandex |
| 2 | 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 | yandex |
Загрузка и вывод данных прошел отлично, теперь преобразуем данные
Заменим название в датасетах
data = data.rename(columns={'event.time':'event_date_time','event.name':'event_name','user.id':'user_id'})
sources=sources.rename(columns={'userId':'user_id'})
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 74197 entries, 0 to 74196 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event_date_time 74197 non-null object 1 event_name 74197 non-null object 2 user_id 74197 non-null object dtypes: object(3) memory usage: 1.7+ MB
Заменим тип в event_date_time на datetime64
data['event_date_time'] = pd.to_datetime(data['event_date_time'])
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 74197 entries, 0 to 74196 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event_date_time 74197 non-null datetime64[ns] 1 event_name 74197 non-null object 2 user_id 74197 non-null object dtypes: datetime64[ns](1), object(2) memory usage: 1.7+ MB
display(data.head(3))
display(sources.head(3))
| event_date_time | event_name | user_id | |
|---|---|---|---|
| 0 | 2019-10-07 00:00:00.431357 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 1 | 2019-10-07 00:00:01.236320 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 2 | 2019-10-07 00:00:02.245341 | tips_show | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
| user_id | source | |
|---|---|---|
| 0 | 020292ab-89bc-4156-9acf-68bc2783f894 | other |
| 1 | cf7eda61-9349-469f-ac27-e5b6f5ec475c | yandex |
| 2 | 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 | yandex |
Наименование колонок изменено, тип данных измен на datetime64
Проверим на дубликаты и пропуски
data.duplicated().sum()
0
sources.duplicated().sum()
0
Явные дубликаты отстутствуют
data.isna().sum()
event_date_time 0 event_name 0 user_id 0 dtype: int64
sources.isna().sum()
user_id 0 source 0 dtype: int64
Пропуски в датасетах отсутсвуют
У нас имеются contacts_show и show_contacts — посмотрел номер телефона,заменим на одно значение-contacts_show
Действительно, данные события необходимо объединить
</div>
data['event_name'].value_counts()
tips_show 40055 photos_show 10012 advert_open 6164 contacts_show 4450 map 3881 search_1 3506 favorites_add 1417 search_5 1049 tips_click 814 search_4 701 contacts_call 541 search_3 522 search_6 460 search_2 324 search_7 222 show_contacts 79 Name: event_name, dtype: int64
data.loc[(data.query('event_name == "show_contacts"').index),'event_name'] = "contacts_show"
data['event_name'].value_counts()
tips_show 40055 photos_show 10012 advert_open 6164 contacts_show 4529 map 3881 search_1 3506 favorites_add 1417 search_5 1049 tips_click 814 search_4 701 contacts_call 541 search_3 522 search_6 460 search_2 324 search_7 222 Name: event_name, dtype: int64
Замена произошла, теперь показ контактов- в одной строке. Так же мы видим, что tips_show — увидел рекомендованные объявления,являются самым популярным действием.
Виды действий:
Посмотрим уникальных пользователей в наших датасетах
print('Количество уникальных пользователей в датасете data',data['user_id'].nunique())
print('Количество уникальных пользователей в датасете sources',sources['user_id'].nunique())
Количество уникальных пользователей в датасете data 4293 Количество уникальных пользователей в датасете sources 4293
Для простоты, можем объединить таблицы
data_2 = data.merge(sources, on='user_id',how='left')
data_2.head(5)
| event_date_time | event_name | user_id | source | |
|---|---|---|---|---|
| 0 | 2019-10-07 00:00:00.431357 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 | other |
| 1 | 2019-10-07 00:00:01.236320 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 | other |
| 2 | 2019-10-07 00:00:02.245341 | tips_show | cf7eda61-9349-469f-ac27-e5b6f5ec475c | yandex |
| 3 | 2019-10-07 00:00:07.039334 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 | other |
| 4 | 2019-10-07 00:00:56.319813 | advert_open | cf7eda61-9349-469f-ac27-e5b6f5ec475c | yandex |
data_2.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 74197 entries, 0 to 74196 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event_date_time 74197 non-null datetime64[ns] 1 event_name 74197 non-null object 2 user_id 74197 non-null object 3 source 74197 non-null object dtypes: datetime64[ns](1), object(3) memory usage: 2.8+ MB
Отлично, пропуски отсутствуют
Перед процедурой выделения сессий необходимо отсортировать датасет по user_id и event_date_time
data_2.sort_values(['user_id', 'event_date_time'])
| event_date_time | event_name | user_id | source | |
|---|---|---|---|---|
| 805 | 2019-10-07 13:39:45.989359 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other |
| 806 | 2019-10-07 13:40:31.052909 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other |
| 809 | 2019-10-07 13:41:05.722489 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other |
| 820 | 2019-10-07 13:43:20.735461 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other |
| 830 | 2019-10-07 13:45:30.917502 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other |
| ... | ... | ... | ... | ... |
| 72584 | 2019-11-03 15:51:23.959572 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | |
| 72589 | 2019-11-03 15:51:57.899997 | contacts_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | |
| 72684 | 2019-11-03 16:07:40.932077 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | |
| 72688 | 2019-11-03 16:08:18.202734 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | |
| 72689 | 2019-11-03 16:08:25.388712 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b |
74197 rows × 4 columns
Добавляем сеессии, где таум аут возьмем 30 мин, тк это время часто используют в статьях
А определенный промежуток времени - это нечто иное, как длительность сеанса (время ожидания сеанса), или как его еще называют, тайм-аут сеанса (session timeout). По умолчанию в Universal Analytics время ожидания сеанса составляет 30 минут.
Ссылка на сайт,https://www.affde.com/ru/what-is-a-session-in-google-analytics-1.html
👍
</div>
session = data_2.copy(deep=True)
session = session.sort_values(['user_id', 'event_date_time'])
g = (session.groupby('user_id')['event_date_time'].diff() > pd.Timedelta('30Min')).cumsum()
session['session_id'] = session.groupby(['user_id', g], sort=False).ngroup() + 1
session
| event_date_time | event_name | user_id | source | session_id | |
|---|---|---|---|---|---|
| 805 | 2019-10-07 13:39:45.989359 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 806 | 2019-10-07 13:40:31.052909 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 809 | 2019-10-07 13:41:05.722489 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 820 | 2019-10-07 13:43:20.735461 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 830 | 2019-10-07 13:45:30.917502 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| ... | ... | ... | ... | ... | ... |
| 72584 | 2019-11-03 15:51:23.959572 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72589 | 2019-11-03 15:51:57.899997 | contacts_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72684 | 2019-11-03 16:07:40.932077 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72688 | 2019-11-03 16:08:18.202734 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72689 | 2019-11-03 16:08:25.388712 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 |
74197 rows × 5 columns
session.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 74197 entries, 805 to 72689 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event_date_time 74197 non-null datetime64[ns] 1 event_name 74197 non-null object 2 user_id 74197 non-null object 3 source 74197 non-null object 4 session_id 74197 non-null int64 dtypes: datetime64[ns](1), int64(1), object(3) memory usage: 3.4+ MB
Сессии сами по себе выделены корректно
</div>
Проверка на выбросы и анамалии
user_event = session.pivot_table(index='user_id', aggfunc={'event_name':'count'})
user_event.columns = ['event_count']
display('Минимальное количесвто событий на одного пользователя:',user_event['event_count'].min())
display('Максимум событий на одного пользователя:',user_event['event_count'].max())
'Минимальное количесвто событий на одного пользователя:'
1
'Максимум событий на одного пользователя:'
478
user_event.describe()
| event_count | |
|---|---|
| count | 4293.000000 |
| mean | 17.283252 |
| std | 29.130677 |
| min | 1.000000 |
| 25% | 5.000000 |
| 50% | 9.000000 |
| 75% | 17.000000 |
| max | 478.000000 |
fig = plt.figure(figsize =(40, 20))
session.pivot_table(index='user_id', columns='event_name',aggfunc='count').boxplot()
plt.ylabel('Количество')
plt.xlabel('Виды действий')
plt.title('Ящики с усами по действиям')
plt.xticks(rotation=45)
plt.show()
Видим, что в каждой группе есть аномалии, учтем это при выводах
Проверка временного пириода в датасете
print('Максимальная дата:',session['event_date_time'].dt.strftime('%Y-%m-%d').max())
Максимальная дата: 2019-11-03
print('Минимальная дата:',session['event_date_time'].dt.strftime('%Y-%m-%d').min())
Минимальная дата: 2019-10-07
print('Временной период =', (session['event_date_time'].max() - session['event_date_time'].min()))
Временной период = 27 days 23:58:12.101130
session['event_date_time'].hist(bins = 30).set_title('Количество событий на протяжении анализа',fontsize=20)
plt.xlabel("Дата", fontsize=12)
plt.xticks(rotation=70)
plt.ylabel('Кол-во событий', fontsize=12)
plt.show()
Начало периода анализа данных с 7 октября 2019 года - 3 ноября 2019 года. 28 дней.
print('Количество пользователей:', session['user_id'].nunique())
print('Количество событий:', session['event_name'].count())
Количество пользователей: 4293 Количество событий: 74197
print('Количество событий, совершающий один пользователь -',round(session['event_name'].count()/session['user_id'].nunique()))
Количество событий, совершающий один пользователь - 17
show_contact = session.query('event_name == "contacts_show"').groupby('user_id')['event_name'].count().sort_values(ascending=False)
print('Количество пользователей ,совершившие просмотр контактов =' , len(show_contact))
Количество пользователей ,совершившие просмотр контактов = 981
show_contact.describe()
count 981.000000 mean 4.616718 std 9.621611 min 1.000000 25% 1.000000 50% 2.000000 75% 4.000000 max 137.000000 Name: event_name, dtype: float64
Мы имеем, уникальных пользователей 4293, из них 981 просматривают контакт. В среднем пользователь на просмотр контакта из 981 совершают просмотр контактов- 4 раза
count_event = session.pivot_table(index='event_name',values='user_id',aggfunc='count').sort_values(by='user_id',
ascending=False).reset_index()
count_event
| event_name | user_id | |
|---|---|---|
| 0 | tips_show | 40055 |
| 1 | photos_show | 10012 |
| 2 | advert_open | 6164 |
| 3 | contacts_show | 4529 |
| 4 | map | 3881 |
| 5 | search_1 | 3506 |
| 6 | favorites_add | 1417 |
| 7 | search_5 | 1049 |
| 8 | tips_click | 814 |
| 9 | search_4 | 701 |
| 10 | contacts_call | 541 |
| 11 | search_3 | 522 |
| 12 | search_6 | 460 |
| 13 | search_2 | 324 |
| 14 | search_7 | 222 |
fig = px.bar(count_event, x='event_name', y='user_id', text ='user_id', color='event_name',
title = 'Количество событий всех пользователей'
)
fig.update_layout(xaxis_title = 'Категория события',
yaxis_title = 'Количество пользователей')
fig.show()
Больше всего событий происходит у пользователя- увидел рекламное объявление.Как и в любом приложении, на главной странице показаны рекомендованные объявления и пользователь открыв приложение, сразу попадает на них.
show_contact = session.query('event_name == "contacts_show"').groupby('user_id')['event_name'].count().sort_values(ascending=False)
print('Количество пользователей ,совершившие просмотр контактов =' , len(show_contact))
Количество пользователей ,совершившие просмотр контактов = 981
print('Количество пользователей:', session['user_id'].nunique())
Количество пользователей: 4293
print('До события совершил просмотр контактов, доходят',len(show_contact)/session['user_id'].nunique() * 100)
До события совершил просмотр контактов, доходят 22.851153039832283
event_count = session.groupby('user_id')[['event_name']].count().reset_index()
print(f"Количество пользователей, кто совершившил одно действие: {len(event_count[event_count['event_name'] == 1])}")
Количество пользователей, кто совершившил одно действие: 65
event_count.describe()
| event_name | |
|---|---|
| count | 4293.000000 |
| mean | 17.283252 |
| std | 29.130677 |
| min | 1.000000 |
| 25% | 5.000000 |
| 50% | 9.000000 |
| 75% | 17.000000 |
| max | 478.000000 |
Мы видим, что среднее значение событий, который совершает один пользователь равен 17. Так же есть, пользователи, которые совершили лишь одно действие, таких пользователей можно удалить, так как они нам ничего не показывают.
session.groupby(['user_id'])['event_name'].nunique().reset_index()['event_name'].value_counts()
2 1365 1 1153 3 875 4 486 5 232 6 105 7 39 8 29 9 7 10 1 11 1 Name: event_name, dtype: int64
drop_one_events= list(event_count[event_count['event_name'] == 1]['user_id'].unique())
session = session.query('user_id not in @drop_one_events')
Вывод: из всех пользователей (4293) лишь 23 % просматривают контакты.
👍
</div>
Сравним значения, тех пользователей, которые совершали просмотр контактов и тех, кто не совершал просмотр контактов
Создадим столбец, в котором указано пользователи, которые не совершили просмотр контактов.
user_id_show_contact= session.query('event_name == "contacts_show"')['user_id']
user_id_show_contact= list(user_id_show_contact.unique())
id_show_contact = session.query("user_id in @user_id_show_contact")
id_show_contact = id_show_contact.pivot_table(index='event_name',values='user_id',aggfunc='count')\
.rename(columns={'user_id':'user_show'})\
.sort_values(by='user_show',
ascending=False).reset_index()
id_show_contact
| event_name | user_show | |
|---|---|---|
| 0 | tips_show | 12768 |
| 1 | contacts_show | 4523 |
| 2 | photos_show | 3828 |
| 3 | advert_open | 1589 |
| 4 | search_1 | 1341 |
| 5 | map | 1101 |
| 6 | contacts_call | 541 |
| 7 | favorites_add | 424 |
| 8 | tips_click | 333 |
| 9 | search_5 | 249 |
| 10 | search_4 | 149 |
| 11 | search_3 | 144 |
| 12 | search_2 | 96 |
| 13 | search_6 | 74 |
| 14 | search_7 | 31 |
session_not_see_contact = session.query("user_id not in @id_show_contact")
session_not_see_contact = session_not_see_contact.pivot_table(index='event_name',values='user_id',
aggfunc='count')\
.rename(columns={'user_id':'user_no_show_contact'})\
.sort_values(by='user_no_show_contact',
ascending=False).reset_index()
session_not_see_contact
| event_name | user_no_show_contact | |
|---|---|---|
| 0 | tips_show | 40025 |
| 1 | photos_show | 10008 |
| 2 | advert_open | 6163 |
| 3 | contacts_show | 4523 |
| 4 | map | 3865 |
| 5 | search_1 | 3502 |
| 6 | favorites_add | 1417 |
| 7 | search_5 | 1047 |
| 8 | tips_click | 814 |
| 9 | search_4 | 701 |
| 10 | contacts_call | 541 |
| 11 | search_3 | 522 |
| 12 | search_6 | 460 |
| 13 | search_2 | 324 |
| 14 | search_7 | 220 |
Посмотрим доли совершения событий из общего числа событий для пользователей, ĸто видел ĸонтаĸты и ĸто не видел контаĸты
пользователи, которые не видели контакты
session_not_see_contact['no_show_proz'] = session_not_see_contact['user_no_show_contact']/session_not_see_contact['user_no_show_contact'].sum()
session_not_see_contact.style.format({'share':'{:.2%}'})
| event_name | user_no_show_contact | no_show_proz | |
|---|---|---|---|
| 0 | tips_show | 40025 | 0.539915 |
| 1 | photos_show | 10008 | 0.135002 |
| 2 | advert_open | 6163 | 0.083135 |
| 3 | contacts_show | 4523 | 0.061013 |
| 4 | map | 3865 | 0.052137 |
| 5 | search_1 | 3502 | 0.047240 |
| 6 | favorites_add | 1417 | 0.019115 |
| 7 | search_5 | 1047 | 0.014123 |
| 8 | tips_click | 814 | 0.010980 |
| 9 | search_4 | 701 | 0.009456 |
| 10 | contacts_call | 541 | 0.007298 |
| 11 | search_3 | 522 | 0.007041 |
| 12 | search_6 | 460 | 0.006205 |
| 13 | search_2 | 324 | 0.004371 |
| 14 | search_7 | 220 | 0.002968 |
кто видел контакты
id_show_contact['show_proz'] = id_show_contact['user_show'] / id_show_contact['user_show'].sum()
id_show_contact.style.format({'share':'{:.2%}'})
| event_name | user_show | show_proz | |
|---|---|---|---|
| 0 | tips_show | 12768 | 0.469567 |
| 1 | contacts_show | 4523 | 0.166342 |
| 2 | photos_show | 3828 | 0.140782 |
| 3 | advert_open | 1589 | 0.058438 |
| 4 | search_1 | 1341 | 0.049318 |
| 5 | map | 1101 | 0.040491 |
| 6 | contacts_call | 541 | 0.019896 |
| 7 | favorites_add | 424 | 0.015593 |
| 8 | tips_click | 333 | 0.012247 |
| 9 | search_5 | 249 | 0.009157 |
| 10 | search_4 | 149 | 0.005480 |
| 11 | search_3 | 144 | 0.005296 |
| 12 | search_2 | 96 | 0.003531 |
| 13 | search_6 | 74 | 0.002721 |
| 14 | search_7 | 31 | 0.001140 |
see_show_user = session_not_see_contact.merge(id_show_contact, on='event_name', how='outer')
see_show_user_proz = see_show_user[['event_name' ,'no_show_proz','show_proz']]
see_show_user_proz
| event_name | no_show_proz | show_proz | |
|---|---|---|---|
| 0 | tips_show | 0.539915 | 0.469567 |
| 1 | photos_show | 0.135002 | 0.140782 |
| 2 | advert_open | 0.083135 | 0.058438 |
| 3 | contacts_show | 0.061013 | 0.166342 |
| 4 | map | 0.052137 | 0.040491 |
| 5 | search_1 | 0.047240 | 0.049318 |
| 6 | favorites_add | 0.019115 | 0.015593 |
| 7 | search_5 | 0.014123 | 0.009157 |
| 8 | tips_click | 0.010980 | 0.012247 |
| 9 | search_4 | 0.009456 | 0.005480 |
| 10 | contacts_call | 0.007298 | 0.019896 |
| 11 | search_3 | 0.007041 | 0.005296 |
| 12 | search_6 | 0.006205 | 0.002721 |
| 13 | search_2 | 0.004371 | 0.003531 |
| 14 | search_7 | 0.002968 | 0.001140 |
ax=see_show_user_proz.plot(x="event_name", y=["no_show_proz", "show_proz"],
kind="bar",figsize=(15,8))
ax.set_title("Доли совершения событий из общего числа событий для пользователей"),
ax.set_xlabel("События"),
ax.set_ylabel("Доли совершения событий"),
plt.show()
👍
</div>
Здесь мы можем увидеть, просмотр фото, поиск по сайту (search_1), а так же просмотр контактов и совершение звонков, больше делали пользователи, которые просматривали контакты.
Посмотрим, какие источники у нас есть, а так же посчитаем какое количество уникальных пользователей приходят в приложении с этих источников
channel = session.pivot_table(index='source',values='user_id',aggfunc='nunique').reset_index()
source_users = channel.copy(deep=True)
channel
| source | user_id | |
|---|---|---|
| 0 | 1117 | |
| 1 | other | 1206 |
| 2 | yandex | 1905 |
channel.plot(x="source", y='user_id',kind="bar",figsize=(15,8),title='Распределение количества просмотра контактов по источникам ')
plt.xlabel("Источник, прихода пользователей"),
plt.ylabel("Количество пользователей"),
plt.show()
Яндекс, лидер. Больше всего пользователей приходят имеенно с этого канала.
Посмотрим, какой канал, привлек пользоватлец, которые больше, совершают просмотр контактов
show_contact_channel= session.query('event_name == "contacts_show"')
channel=show_contact_channel.pivot_table(index='source',values='event_name',aggfunc='count').reset_index()
show_contact_channel = channel.copy(deep=True)
show_contact_channel
| source | event_name | |
|---|---|---|
| 0 | 1439 | |
| 1 | other | 1051 |
| 2 | yandex | 2033 |
show_contact_channel.plot(x="source", y='event_name',kind="bar",figsize=(15,8),title='Распределение количества просмотра контактов по источникам ')
plt.xlabel("Источник, прихода пользователей"),
plt.ylabel("Количество пользователей"),
plt.show()
Здесь, так же мы видим, что пользователи, которые пришли с YANDEX больше всего посмотрели контакты.
Определим минимальное и максимальное количество сессий на пользователя
max_count_session=session.groupby('user_id').agg({'session_id':'nunique'}).max()
print('Максимальное количество сессий на пользователя',max_count_session)
Максимальное количество сессий на пользователя session_id 99 dtype: int64
mim_count_session=session.groupby('user_id').agg({'session_id':'nunique'}).min()
print('Минимальное количество сессий на пользователя',mim_count_session)
Минимальное количество сессий на пользователя session_id 1 dtype: int64
avg_count_session=session.pivot_table(index='user_id', values='session_id', aggfunc='count').median()
print('Среднее количество сессий на пользователя',avg_count_session)
Среднее количество сессий на пользователя session_id 9.0 dtype: float64
time_session=session.pivot_table(index='session_id',values='event_date_time', aggfunc=['max','min']).reset_index()
time_session.columns=['session_id','max_time_session','min_time_session']
time_session['time_sessions'] = time_session['max_time_session'] - time_session['min_time_session']
print('Средняя продолжительность сессии',time_session['time_sessions'].median())
Средняя продолжительность сессии 0 days 00:05:41.153871
Вывод:
Данные показатели рассчитаны верно
</div>
Посмотрим, какие первые события делали, все пользователи
first_event = session.pivot_table(index=['user_id','event_name'],values='event_date_time',aggfunc=['min','count'])
first_event .columns = ['first_event_time','count_event']
first_event = first_event .sort_values(by='first_event_time').reset_index()
first_event
| user_id | event_name | first_event_time | count_event | |
|---|---|---|---|---|
| 0 | 020292ab-89bc-4156-9acf-68bc2783f894 | advert_open | 2019-10-07 00:00:00.431357 | 10 |
| 1 | 020292ab-89bc-4156-9acf-68bc2783f894 | tips_show | 2019-10-07 00:00:01.236320 | 15 |
| 2 | cf7eda61-9349-469f-ac27-e5b6f5ec475c | tips_show | 2019-10-07 00:00:02.245341 | 59 |
| 3 | cf7eda61-9349-469f-ac27-e5b6f5ec475c | advert_open | 2019-10-07 00:00:56.319813 | 19 |
| 4 | 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 | tips_show | 2019-10-07 00:02:07.374346 | 87 |
| ... | ... | ... | ... | ... |
| 10761 | c10055f0-0b47-477a-869e-d391b31fdf8f | contacts_show | 2019-11-03 23:46:31.298524 | 1 |
| 10762 | d157bffc-264d-4464-8220-1cc0c42f43a9 | map | 2019-11-03 23:46:47.068179 | 1 |
| 10763 | d157bffc-264d-4464-8220-1cc0c42f43a9 | advert_open | 2019-11-03 23:46:58.914787 | 2 |
| 10764 | d157bffc-264d-4464-8220-1cc0c42f43a9 | tips_show | 2019-11-03 23:47:01.232230 | 2 |
| 10765 | c10055f0-0b47-477a-869e-d391b31fdf8f | tips_click | 2019-11-03 23:48:47.344430 | 4 |
10766 rows × 4 columns
first_event = first_event.pivot_table(index='event_name',values='user_id',aggfunc=['first','count']).reset_index()
first_event.columns=['event_name','user','count']
first_event.sort_values(by='count', ascending=False)
| event_name | user | count | |
|---|---|---|---|
| 14 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 | 2771 |
| 4 | map | 020292ab-89bc-4156-9acf-68bc2783f894 | 1440 |
| 5 | photos_show | c2cf55c0-95f7-4269-896c-931d14deaab5 | 1091 |
| 2 | contacts_show | 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 | 975 |
| 6 | search_1 | 48e614d6-fe03-40f7-bf9e-4c4f61c19f64 | 783 |
| 0 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 | 750 |
| 10 | search_5 | d9b06b47-0f36-419b-bbb0-3533e582a6cb | 661 |
| 9 | search_4 | d9b06b47-0f36-419b-bbb0-3533e582a6cb | 474 |
| 3 | favorites_add | 59013b73-1a5e-4ac0-97dd-81b1a10c8186 | 351 |
| 11 | search_6 | 1df586e9-5be6-4b10-9316-a6f5d4fb317d | 330 |
| 13 | tips_click | 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 | 322 |
| 7 | search_2 | af9f4d75-530c-40f9-9aab-a5535b84845e | 242 |
| 1 | contacts_call | be95e2a4-50e3-4763-bc00-1758848d2641 | 213 |
| 8 | search_3 | 0d19961e-f238-4fb3-84aa-d707e2b626ac | 208 |
| 12 | search_7 | 1df586e9-5be6-4b10-9316-a6f5d4fb317d | 155 |
fig = px.bar(first_event.sort_values(by='count', ascending=False), x='event_name', y='count', text ='count', color='event_name',
title = 'Количество первых событий '
)
fig.update_layout(xaxis_title = 'Категория события',
yaxis_title = 'Количество событий')
fig.show()
👍
</div>
Самое популярное событий это tips_show -увидел рекомендованные объявления, следовательно как только пользователь открывает приложение, первон что он видит, это рекомендованные товары. Не популярны действия такие, как search_2,3,7 — разные действия, связанные с поиском по сайту, contacts_call — позвонил по номеру из объявления. Можем сделать вывод, что через приложение пользователи редко совершают звонки.
Теперь посмотрим, какие действия, совершают пользователи, которые просматривают контакты
Для этого создадим список, уникальных пользователей, которые совершили просмотр контактов, посчитаем количество событий, которые они совершают, и отобразим от большего к меньшему
user_id_show_contact= session.query('event_name == "contacts_show"')['user_id']
id_show_contact = session.query("user_id in @user_id_show_contact")
id_show_contact = id_show_contact.pivot_table(index='event_name',values='user_id',aggfunc='count')\
.sort_values(by='user_id',ascending=False).reset_index()
id_show_contact
| event_name | user_id | |
|---|---|---|
| 0 | tips_show | 12768 |
| 1 | contacts_show | 4523 |
| 2 | photos_show | 3828 |
| 3 | advert_open | 1589 |
| 4 | search_1 | 1341 |
| 5 | map | 1101 |
| 6 | contacts_call | 541 |
| 7 | favorites_add | 424 |
| 8 | tips_click | 333 |
| 9 | search_5 | 249 |
| 10 | search_4 | 149 |
| 11 | search_3 | 144 |
| 12 | search_2 | 96 |
| 13 | search_6 | 74 |
| 14 | search_7 | 31 |
fig = px.bar(id_show_contact, x='event_name', y='user_id', text ='user_id', color='event_name',
title = 'Количество событий пользователя,которые просматривают контакты'
)
fig.update_layout(xaxis_title = 'Категория события',
yaxis_title = 'Количество событий')
fig.show()
Здесь мы видим, что пользователи, которые просматривают контакты,больше всего действий совершают -увидел рекомендованные объявления, посмотрел контакт, увидел фото, открыл карточку объявления, а так же пользовался поиском по сайту.Самое неохотное действие это search_2 — search_7(поиск по сайту).
Вычислим количество пользователей, ĸто совершил просмотр контактов каждого события
cout_user_event = session.pivot_table(index='event_name', values='user_id',aggfunc='nunique').reset_index()
cout_user_event
| event_name | user_id | |
|---|---|---|
| 0 | advert_open | 750 |
| 1 | contacts_call | 213 |
| 2 | contacts_show | 975 |
| 3 | favorites_add | 351 |
| 4 | map | 1440 |
| 5 | photos_show | 1091 |
| 6 | search_1 | 783 |
| 7 | search_2 | 242 |
| 8 | search_3 | 208 |
| 9 | search_4 | 474 |
| 10 | search_5 | 661 |
| 11 | search_6 | 330 |
| 12 | search_7 | 155 |
| 13 | tips_click | 322 |
| 14 | tips_show | 2771 |
Рассчитаем ĸонверсию из ĸаждого события в целевое дейсвтия, просмотр контакта.
Для этого мы построим сводную таблицу, исключим просмотр контактов и создадим лист, далее мы отфильтруем по session_id'. Посчитаем конверсию и узнаем какие действия больще всего совершают пользователи, которые просматривают контакты
all_users = session.pivot_table(index=['user_id','event_name'], values='event_date_time', aggfunc='first').reset_index()
all_users = all_users.query('event_name == "contacts_show"')
all_users_list = list(all_users['user_id'])
all_users
| user_id | event_name | event_date_time | |
|---|---|---|---|
| 4 | 00157779-810c-4498-9e05-a1e9e3cedf93 | contacts_show | 2019-10-20 19:17:18.659799 |
| 18 | 00551e79-152e-4441-9cf7-565d7eb04090 | contacts_show | 2019-10-25 16:44:41.263364 |
| 24 | 005fbea5-2678-406f-88a6-fbe9787e2268 | contacts_show | 2019-10-11 11:22:54.442841 |
| 29 | 00753c79-ea81-4456-acd0-a47a23ca2fb9 | contacts_show | 2019-10-20 14:57:06.080501 |
| 32 | 007d031d-5018-4e02-b7ee-72a30609173f | contacts_show | 2019-10-22 13:08:09.140381 |
| ... | ... | ... | ... |
| 10728 | fee3ba1c-16f4-46f7-bf56-4bf80cc4e2f5 | contacts_show | 2019-10-26 12:13:19.781554 |
| 10740 | ff1554b5-919e-40b1-90bb-ee1f7f6d5846 | contacts_show | 2019-10-21 10:59:23.944891 |
| 10756 | ffc01466-fdb1-4460-ae94-e800f52eb136 | contacts_show | 2019-10-07 20:33:42.135500 |
| 10760 | ffe68f10-e48e-470e-be9b-eeb93128ff1a | contacts_show | 2019-10-22 16:07:17.683553 |
| 10763 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | contacts_show | 2019-10-16 12:57:40.598641 |
975 rows × 3 columns
all_users_con = session.query('user_id in @all_users_list')
all_users_con = all_users_con.pivot_table(index='event_name', values='user_id',
aggfunc='nunique').reset_index()
all_users_con.columns=['event_name','convert']
all_users_con = all_users_con.query('event_name != "contacts_call" and event_name != "contacts_show"')
all_users_con
| event_name | convert | |
|---|---|---|
| 0 | advert_open | 138 |
| 3 | favorites_add | 136 |
| 4 | map | 289 |
| 5 | photos_show | 339 |
| 6 | search_1 | 237 |
| 7 | search_2 | 55 |
| 8 | search_3 | 38 |
| 9 | search_4 | 88 |
| 10 | search_5 | 114 |
| 11 | search_6 | 52 |
| 12 | search_7 | 25 |
| 13 | tips_click | 100 |
| 14 | tips_show | 516 |
Вычислим ĸоличество пользователей, ĸто в целом совершил событие
all_users_count_events = session.pivot_table(index='event_name', values='user_id',aggfunc='nunique').reset_index()
all_users_count_events.columns=['event_name','total_users']
all_users_count_events = all_users_count_events.query('event_name != "contacts_call" and \
event_name != "contacts_show"')
all_users_count_events
| event_name | total_users | |
|---|---|---|
| 0 | advert_open | 750 |
| 3 | favorites_add | 351 |
| 4 | map | 1440 |
| 5 | photos_show | 1091 |
| 6 | search_1 | 783 |
| 7 | search_2 | 242 |
| 8 | search_3 | 208 |
| 9 | search_4 | 474 |
| 10 | search_5 | 661 |
| 11 | search_6 | 330 |
| 12 | search_7 | 155 |
| 13 | tips_click | 322 |
| 14 | tips_show | 2771 |
Соединяем 2 таблицы
session_cr = all_users_count_events.merge(all_users_con, on='event_name')
session_cr['cr'] = session_cr['convert'] / session_cr['total_users']
session_cr.sort_values(by='cr', ascending=False)
| event_name | total_users | convert | cr | |
|---|---|---|---|---|
| 1 | favorites_add | 351 | 136 | 0.387464 |
| 3 | photos_show | 1091 | 339 | 0.310724 |
| 11 | tips_click | 322 | 100 | 0.310559 |
| 4 | search_1 | 783 | 237 | 0.302682 |
| 5 | search_2 | 242 | 55 | 0.227273 |
| 2 | map | 1440 | 289 | 0.200694 |
| 12 | tips_show | 2771 | 516 | 0.186214 |
| 7 | search_4 | 474 | 88 | 0.185654 |
| 0 | advert_open | 750 | 138 | 0.184000 |
| 6 | search_3 | 208 | 38 | 0.182692 |
| 8 | search_5 | 661 | 114 | 0.172466 |
| 10 | search_7 | 155 | 25 | 0.161290 |
| 9 | search_6 | 330 | 52 | 0.157576 |
fig = px.bar(session_cr.sort_values(by='cr', ascending=False), x='event_name', y='cr', text ='event_name', color='event_name',
title = 'Количество событий пользователя,которые просматривают контакты'
)
fig.update_layout(xaxis_title = 'Категория события',
yaxis_title = 'Конверсия событий')
fig.show()
Отлично. Теперь конверсия рассчитана правильно
</div>
Из графика видим, что конверсия у просмотра контактов, те кто сохраняет объявления в избранное больше. Следовательно, пользователи больше просмаривают контакты тех объявлений, которые сохранили
Возьмем пользователей, которые совершали просмотр контактов
session_show_contact= session.query("user_id in @user_id_show_contact")
session_show_contact
| event_date_time | event_name | user_id | source | session_id | |
|---|---|---|---|---|---|
| 31632 | 2019-10-19 21:34:33.849769 | search_1 | 00157779-810c-4498-9e05-a1e9e3cedf93 | yandex | 5 |
| 31636 | 2019-10-19 21:35:19.296599 | search_1 | 00157779-810c-4498-9e05-a1e9e3cedf93 | yandex | 5 |
| 31640 | 2019-10-19 21:36:44.344691 | search_1 | 00157779-810c-4498-9e05-a1e9e3cedf93 | yandex | 5 |
| 31655 | 2019-10-19 21:40:38.990477 | photos_show | 00157779-810c-4498-9e05-a1e9e3cedf93 | yandex | 5 |
| 31659 | 2019-10-19 21:42:13.837523 | photos_show | 00157779-810c-4498-9e05-a1e9e3cedf93 | yandex | 5 |
| ... | ... | ... | ... | ... | ... |
| 72584 | 2019-11-03 15:51:23.959572 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72589 | 2019-11-03 15:51:57.899997 | contacts_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72684 | 2019-11-03 16:07:40.932077 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72688 | 2019-11-03 16:08:18.202734 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72689 | 2019-11-03 16:08:25.388712 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 |
27191 rows × 5 columns
Проанализируем время между просмотр ĸонтаĸтов и первым событием, ĸоторое не является просмотром ĸонтаĸтов
event_contact_show = session_show_contact.query('event_name == "contacts_show"')
event_contact_show.pivot_table(index='user_id',values='event_name', aggfunc='count').reset_index()
| user_id | event_name | |
|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 11 |
| 1 | 00551e79-152e-4441-9cf7-565d7eb04090 | 3 |
| 2 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 3 |
| 3 | 00753c79-ea81-4456-acd0-a47a23ca2fb9 | 1 |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2 |
| ... | ... | ... |
| 970 | fee3ba1c-16f4-46f7-bf56-4bf80cc4e2f5 | 17 |
| 971 | ff1554b5-919e-40b1-90bb-ee1f7f6d5846 | 1 |
| 972 | ffc01466-fdb1-4460-ae94-e800f52eb136 | 1 |
| 973 | ffe68f10-e48e-470e-be9b-eeb93128ff1a | 1 |
| 974 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 68 |
975 rows × 2 columns
Тут мы видим, что из тех, пользователей, а именн 981 , кто совершил целевое действие( просмотр контактов), есть пользователи, кто совершил это действие несколько раз.
Чтобы рассчитать время между соверешнием первого действия и целевого, будем ориентироваться на первое целевое действие.
Создадим таблицу, где будет показано, какое первое действие было у пользователей, кто совершил просмотр контактов.
session_show_contact = session_show_contact.pivot_table(index=['session_id','event_name'],
values='event_date_time',aggfunc='first').reset_index()
session_show_contact
| session_id | event_name | event_date_time | |
|---|---|---|---|
| 0 | 5 | photos_show | 2019-10-19 21:40:38.990477 |
| 1 | 5 | search_1 | 2019-10-19 21:34:33.849769 |
| 2 | 6 | contacts_call | 2019-10-20 19:17:24.887762 |
| 3 | 6 | contacts_show | 2019-10-20 19:17:18.659799 |
| 4 | 6 | favorites_add | 2019-10-20 19:03:02.030004 |
| ... | ... | ... | ... |
| 7528 | 10366 | tips_show | 2019-11-02 19:25:53.794029 |
| 7529 | 10367 | contacts_show | 2019-11-03 14:33:47.921863 |
| 7530 | 10367 | tips_show | 2019-11-03 14:32:55.956301 |
| 7531 | 10368 | contacts_show | 2019-11-03 15:48:05.420247 |
| 7532 | 10368 | tips_show | 2019-11-03 15:36:01.007440 |
7533 rows × 3 columns
Создадим таблицу, где будут события у пользователя, совершивший промотр контактов и так же создаем таблицу по времени.
events_that_viewed = session_show_contact.groupby('session_id')['event_date_time'].min().reset_index()
events_that_viewed.columns = ['session_id','first_event']
events_that_viewed_time = event_contact_show.groupby('session_id')['event_date_time'].min().reset_index()
events_that_viewed_time.columns = ['session_id','first_show_contacts']
Объединю таблицы, затем отфильтрую и посчитаем разницу
time_show_contact = events_that_viewed .merge(events_that_viewed_time,on='session_id').reset_index()
time_show_contact = time_show_contact.query('first_show_contacts > first_event')
time_show_contact['diff'] = (time_show_contact['first_show_contacts'] -time_show_contact['first_event'])
time_show_contact
| index | session_id | first_event | first_show_contacts | diff | |
|---|---|---|---|---|---|
| 0 | 0 | 6 | 2019-10-20 18:49:24.115634 | 2019-10-20 19:17:18.659799 | 0 days 00:27:54.544165 |
| 1 | 1 | 8 | 2019-10-29 21:18:24.850073 | 2019-10-29 21:26:40.258472 | 0 days 00:08:15.408399 |
| 2 | 2 | 9 | 2019-10-30 07:50:45.948358 | 2019-10-30 08:01:05.420773 | 0 days 00:10:19.472415 |
| 5 | 5 | 19 | 2019-10-28 13:08:15.809056 | 2019-10-28 13:10:40.331441 | 0 days 00:02:24.522385 |
| 8 | 8 | 30 | 2019-10-22 13:02:26.636223 | 2019-10-22 13:08:09.140381 | 0 days 00:05:42.504158 |
| ... | ... | ... | ... | ... | ... |
| 1691 | 1691 | 10360 | 2019-10-29 16:12:15.417343 | 2019-10-29 16:13:00.681459 | 0 days 00:00:45.264116 |
| 1693 | 1693 | 10365 | 2019-11-02 18:01:27.094834 | 2019-11-02 18:17:41.386651 | 0 days 00:16:14.291817 |
| 1694 | 1694 | 10366 | 2019-11-02 19:25:53.794029 | 2019-11-02 19:26:07.834494 | 0 days 00:00:14.040465 |
| 1695 | 1695 | 10367 | 2019-11-03 14:32:55.956301 | 2019-11-03 14:33:47.921863 | 0 days 00:00:51.965562 |
| 1696 | 1696 | 10368 | 2019-11-03 15:36:01.007440 | 2019-11-03 15:48:05.420247 | 0 days 00:12:04.412807 |
1160 rows × 5 columns
time_show_contact.describe()
| index | session_id | diff | |
|---|---|---|---|
| count | 1160.000000 | 1160.000000 | 1160 |
| mean | 863.590517 | 5403.843966 | 0 days 00:09:45.569282692 |
| std | 484.904906 | 2969.959565 | 0 days 00:13:38.461736017 |
| min | 0.000000 | 6.000000 | 0 days 00:00:00.158918 |
| 25% | 443.750000 | 2936.750000 | 0 days 00:01:23.731704750 |
| 50% | 876.500000 | 5564.500000 | 0 days 00:04:51.950202500 |
| 75% | 1274.250000 | 7882.250000 | 0 days 00:12:20.300075250 |
| max | 1696.000000 | 10368.000000 | 0 days 03:21:08.091077 |
Вывод, мы видим, что у нас есть почти нулевые значения, это либо погрешность между датасетами, либо, пользователь переходил сразу по ссылке на просмотр контактов. Среднее время, между первым событием и просмотром контактов я возьму за 50% - почти 5 минут. Макксимальное время, это 3 часа 21 мин.
session_3 = session.copy(deep=True)
Объединим, все события search
def events_type(i):
if 'search' in i:
new_name = 'search'
return new_name
return i
session_3['event_name'] = session_3['event_name'].apply(events_type)
session_3['event_name'].value_counts()
tips_show 40025 photos_show 10008 search 6776 advert_open 6163 contacts_show 4523 map 3865 favorites_add 1417 tips_click 814 contacts_call 541 Name: event_name, dtype: int64
session_3
| event_date_time | event_name | user_id | source | session_id | |
|---|---|---|---|---|---|
| 805 | 2019-10-07 13:39:45.989359 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 806 | 2019-10-07 13:40:31.052909 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 809 | 2019-10-07 13:41:05.722489 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 820 | 2019-10-07 13:43:20.735461 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 830 | 2019-10-07 13:45:30.917502 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| ... | ... | ... | ... | ... | ... |
| 72584 | 2019-11-03 15:51:23.959572 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72589 | 2019-11-03 15:51:57.899997 | contacts_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72684 | 2019-11-03 16:07:40.932077 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72688 | 2019-11-03 16:08:18.202734 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72689 | 2019-11-03 16:08:25.388712 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 |
74132 rows × 5 columns
# Удаляем дубликаты событий в сессиях
data = session_3.drop_duplicates(subset=['session_id', 'event_name']).copy()
# Оставляем только необходимые колонки
data = data[['session_id', 'event_name', 'event_date_time']]
# Создаем датафрейм для первого события и находим минимальное время для каждой сессии
first_event = (data[data['event_name'] == 'map']
.groupby('session_id', as_index=False)
.agg(map_event_time=('event_date_time', 'min')))
# Соединяем датафреймы для второго и первого событий
second_event = (data[data['event_name'] == 'contacts_show']
.merge(first_event, on='session_id', how='left'))
# Оставляем только те строки, где время второго события больше времени первого
second_event = second_event[second_event['event_date_time'] > second_event['map_event_time']]
# Переименовываем колонку и удаляем лишнюю
second_event = (second_event.rename(columns={"event_date_time": "contacts_event_time"})
.drop(['event_name'], axis=1))
second_event
| session_id | contacts_event_time | map_event_time | |
|---|---|---|---|
| 8 | 30 | 2019-10-22 13:08:09.140381 | 2019-10-22 13:02:26.636223 |
| 33 | 163 | 2019-10-17 11:03:23.898333 | 2019-10-17 10:56:27.109806 |
| 38 | 183 | 2019-10-22 16:36:48.062582 | 2019-10-22 16:32:47.697266 |
| 43 | 235 | 2019-10-09 13:49:41.825754 | 2019-10-09 13:45:07.605410 |
| 44 | 241 | 2019-10-21 17:30:50.223732 | 2019-10-21 17:26:28.646441 |
| ... | ... | ... | ... |
| 1623 | 9948 | 2019-10-23 12:29:59.741763 | 2019-10-23 12:28:37.779503 |
| 1630 | 9975 | 2019-10-19 19:58:45.717498 | 2019-10-19 19:58:07.577606 |
| 1639 | 10056 | 2019-10-31 12:07:38.629696 | 2019-10-31 12:06:13.151254 |
| 1643 | 10102 | 2019-10-26 08:51:43.850449 | 2019-10-26 08:48:44.633046 |
| 1659 | 10232 | 2019-10-18 14:12:22.557426 | 2019-10-18 14:10:52.847046 |
238 rows × 3 columns
second_event['diff']=second_event['contacts_event_time']-second_event['map_event_time']
second_event
| session_id | contacts_event_time | map_event_time | diff | |
|---|---|---|---|---|
| 8 | 30 | 2019-10-22 13:08:09.140381 | 2019-10-22 13:02:26.636223 | 0 days 00:05:42.504158 |
| 33 | 163 | 2019-10-17 11:03:23.898333 | 2019-10-17 10:56:27.109806 | 0 days 00:06:56.788527 |
| 38 | 183 | 2019-10-22 16:36:48.062582 | 2019-10-22 16:32:47.697266 | 0 days 00:04:00.365316 |
| 43 | 235 | 2019-10-09 13:49:41.825754 | 2019-10-09 13:45:07.605410 | 0 days 00:04:34.220344 |
| 44 | 241 | 2019-10-21 17:30:50.223732 | 2019-10-21 17:26:28.646441 | 0 days 00:04:21.577291 |
| ... | ... | ... | ... | ... |
| 1623 | 9948 | 2019-10-23 12:29:59.741763 | 2019-10-23 12:28:37.779503 | 0 days 00:01:21.962260 |
| 1630 | 9975 | 2019-10-19 19:58:45.717498 | 2019-10-19 19:58:07.577606 | 0 days 00:00:38.139892 |
| 1639 | 10056 | 2019-10-31 12:07:38.629696 | 2019-10-31 12:06:13.151254 | 0 days 00:01:25.478442 |
| 1643 | 10102 | 2019-10-26 08:51:43.850449 | 2019-10-26 08:48:44.633046 | 0 days 00:02:59.217403 |
| 1659 | 10232 | 2019-10-18 14:12:22.557426 | 2019-10-18 14:10:52.847046 | 0 days 00:01:29.710380 |
238 rows × 4 columns
average_time = second_event['diff'].mean()
median_time = second_event['diff'].median()
print(f"Среднее время между map и show_contacts: {average_time}")
Среднее время между map и show_contacts: 0 days 00:11:01.162961117
print(f"Медианное времени между map и show_contacts: {median_time}")
Медианное времени между map и show_contacts: 0 days 00:05:21.764177500
👍
</div>
Вывод:Среднее время между map и show_contacts составило 11 минут,а медианное 5 минут 21 секунду.
# Создаем датафрейм для первого события и находим минимальное время для каждой сессии
first_event_searsh = (data[data['event_name'] == 'search']
.groupby('session_id', as_index=False)
.agg(search_event_time=('event_date_time', 'min')))
# Соединяем датафреймы для второго и первого событий
second_event_contact = (data[data['event_name'] == 'contacts_show']
.merge(first_event_searsh, on='session_id', how='left'))
# Оставляем только те строки, где время второго события больше времени первого
second_event_contact = second_event_contact[second_event_contact['event_date_time'] > second_event_contact['search_event_time']]
# Переименовываем колонку и удаляем лишнюю
second_event_contact = (second_event_contact.rename(columns={"event_date_time": "contacts_event_time"})
.drop(['event_name'], axis=1))
second_event_contact['diff']=second_event_contact['contacts_event_time']-second_event_contact['search_event_time']
second_event_contact
| session_id | contacts_event_time | search_event_time | diff | |
|---|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | 2019-10-20 18:49:24.115634 | 0 days 00:27:54.544165 |
| 1 | 8 | 2019-10-29 21:26:40.258472 | 2019-10-29 21:18:24.850073 | 0 days 00:08:15.408399 |
| 2 | 9 | 2019-10-30 08:01:05.420773 | 2019-10-30 07:50:45.948358 | 0 days 00:10:19.472415 |
| 5 | 19 | 2019-10-28 13:10:40.331441 | 2019-10-28 13:08:15.809056 | 0 days 00:02:24.522385 |
| 13 | 49 | 2019-10-17 16:21:56.290598 | 2019-10-17 16:20:58.368137 | 0 days 00:00:57.922461 |
| ... | ... | ... | ... | ... |
| 1662 | 10239 | 2019-10-19 22:27:47.194789 | 2019-10-19 22:24:48.001563 | 0 days 00:02:59.193226 |
| 1663 | 10240 | 2019-10-12 16:35:29.362577 | 2019-10-12 16:01:32.411784 | 0 days 00:33:56.950793 |
| 1668 | 10264 | 2019-10-27 16:45:32.362229 | 2019-10-27 16:44:21.546669 | 0 days 00:01:10.815560 |
| 1670 | 10277 | 2019-10-30 18:12:06.941037 | 2019-10-30 18:02:30.840095 | 0 days 00:09:36.100942 |
| 1673 | 10317 | 2019-10-26 12:13:19.781554 | 2019-10-26 11:43:11.921954 | 0 days 00:30:07.859600 |
320 rows × 4 columns
mean_value = second_event_contact['diff'].mean()
median_value = second_event_contact['diff'].median()
print('Среднее время между search и show_contacts: ',mean_value)
Среднее время между search и show_contacts: 0 days 00:09:02.771149312
print('Медиана времени между search и show_contacts: ',median_value)
Медиана времени между search и show_contacts: 0 days 00:04:22.133708
👍
</div>
Вывод:Среднее время между search и show_contacts составляет 3 минуты 53 секунды.Медиана времени между search и show_contacts составляет 1 минуту 4 секунды.
Общий вывод: Как различается время между событиями map -> contacts_show и search -> contacts_show в рамках сессий. Папа событий поиска и просмотра контактов быстрее, пользователь, через поиск быстрее переходит на просмотр контакта, нежеле через карту.
events_by_users = session_3.groupby('event_name').agg({'user_id': 'nunique'}).sort_values('user_id', ascending=False).reset_index()
events_by_users.columns = ['event_name', 'cnt_users']
events_by_users
| event_name | cnt_users | |
|---|---|---|
| 0 | tips_show | 2771 |
| 1 | search | 1658 |
| 2 | map | 1440 |
| 3 | photos_show | 1091 |
| 4 | contacts_show | 975 |
| 5 | advert_open | 750 |
| 6 | favorites_add | 351 |
| 7 | tips_click | 322 |
| 8 | contacts_call | 213 |
events_by_users['share'] = events_by_users['cnt_users'] / session_3['user_id'].nunique()
events_by_users.style.format({'share':'{:.2%}'})
| event_name | cnt_users | share | |
|---|---|---|---|
| 0 | tips_show | 2771 | 65.54% |
| 1 | search | 1658 | 39.21% |
| 2 | map | 1440 | 34.06% |
| 3 | photos_show | 1091 | 25.80% |
| 4 | contacts_show | 975 | 23.06% |
| 5 | advert_open | 750 | 17.74% |
| 6 | favorites_add | 351 | 8.30% |
| 7 | tips_click | 322 | 7.62% |
| 8 | contacts_call | 213 | 5.04% |
session_3
| event_date_time | event_name | user_id | source | session_id | |
|---|---|---|---|---|---|
| 805 | 2019-10-07 13:39:45.989359 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 806 | 2019-10-07 13:40:31.052909 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 809 | 2019-10-07 13:41:05.722489 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 820 | 2019-10-07 13:43:20.735461 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 830 | 2019-10-07 13:45:30.917502 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| ... | ... | ... | ... | ... | ... |
| 72584 | 2019-11-03 15:51:23.959572 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72589 | 2019-11-03 15:51:57.899997 | contacts_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72684 | 2019-11-03 16:07:40.932077 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72688 | 2019-11-03 16:08:18.202734 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72689 | 2019-11-03 16:08:25.388712 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 |
74132 rows × 5 columns
e=pd.DataFrame(session_3)
e['event_date_time']=pd.to_datetime(e['event_date_time'])
e.sort_values(by=['session_id','event_date_time'],inplace=True)
e
| event_date_time | event_name | user_id | source | session_id | |
|---|---|---|---|---|---|
| 805 | 2019-10-07 13:39:45.989359 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 806 | 2019-10-07 13:40:31.052909 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 809 | 2019-10-07 13:41:05.722489 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 820 | 2019-10-07 13:43:20.735461 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| 830 | 2019-10-07 13:45:30.917502 | tips_show | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 |
| ... | ... | ... | ... | ... | ... |
| 72584 | 2019-11-03 15:51:23.959572 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72589 | 2019-11-03 15:51:57.899997 | contacts_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72684 | 2019-11-03 16:07:40.932077 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72688 | 2019-11-03 16:08:18.202734 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | |
| 72689 | 2019-11-03 16:08:25.388712 | tips_show | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 |
74132 rows × 5 columns
e['event_unique']=e.groupby('session_id')['event_name'].apply(lambda x: x.mask(x.duplicated()))
e.drop(columns=['event_name'],inplace=True)
e.reset_index(drop=True,inplace=True)
e.sort_values(by='session_id', ascending=False)
| event_date_time | user_id | source | session_id | event_unique | |
|---|---|---|---|---|---|
| 74131 | 2019-11-03 16:08:25.388712 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | NaN | |
| 74124 | 2019-11-03 15:50:01.773945 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | NaN | |
| 74118 | 2019-11-03 15:36:01.007440 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | tips_show | |
| 74119 | 2019-11-03 15:36:29.041818 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | NaN | |
| 74120 | 2019-11-03 15:37:18.264804 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 10368 | NaN | |
| ... | ... | ... | ... | ... | ... |
| 1 | 2019-10-07 13:40:31.052909 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 | NaN |
| 6 | 2019-10-07 13:46:31.033718 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 | NaN |
| 7 | 2019-10-07 13:47:32.860234 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 | NaN |
| 8 | 2019-10-07 13:49:41.716617 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 | NaN |
| 0 | 2019-10-07 13:39:45.989359 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 1 | tips_show |
74132 rows × 5 columns
Распределение уникальных событий (переходов) в разрезе сессий и выбрать подходящее количество шагов.
e.sort_values(['session_id','event_unique'],inplace=True)
e.reset_index(drop=True,inplace=True)
event_distraction=e.groupby('session_id')['event_unique'].nunique()
min_steps=event_distraction.min()
max_steps=event_distraction.max()
avg_steps=event_distraction.mean()
print('Минимальное число шагов',min_steps)
print('Максимальное число шагов',max_steps)
print('Среднее число шагов',avg_steps)
Минимальное число шагов 1 Максимальное число шагов 6 Среднее число шагов 1.7265844899543823
def add_features(df):
"""Функция генерации новых столбцов для исходной таблицы
Args:
df (pd.DataFrame): исходная таблица.
Returns:
pd.DataFrame: таблица с новыми признаками.
"""
# сортируем по id и времени
sorted_df = df.sort_values(by=['session_id', 'event_date_time']).copy()
# добавляем шаги событий
sorted_df['step'] = sorted_df.groupby('session_id').cumcount() + 1
# добавляем узлы-источники и целевые узлы
# узлы-источники - это сами события
sorted_df['source'] = sorted_df['event_unique']
# добавляем целевые узлы
sorted_df['target'] = sorted_df.groupby('session_id')['source'].shift(-1)
# возврат таблицы без имени событий
return sorted_df.drop(['event_unique'], axis=1)
# преобразуем таблицу
e = add_features(e)
e.head(10)
| event_date_time | user_id | source | session_id | step | target | |
|---|---|---|---|---|---|---|
| 0 | 2019-10-07 13:39:45.989359 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | tips_show | 1 | 1 | NaN |
| 1 | 2019-10-07 13:40:31.052909 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | NaN | 1 | 2 | NaN |
| 2 | 2019-10-07 13:41:05.722489 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | NaN | 1 | 3 | NaN |
| 3 | 2019-10-07 13:43:20.735461 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | NaN | 1 | 4 | NaN |
| 4 | 2019-10-07 13:45:30.917502 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | NaN | 1 | 5 | NaN |
| 5 | 2019-10-07 13:45:43.212340 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | NaN | 1 | 6 | NaN |
| 6 | 2019-10-07 13:46:31.033718 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | NaN | 1 | 7 | NaN |
| 7 | 2019-10-07 13:47:32.860234 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | NaN | 1 | 8 | NaN |
| 8 | 2019-10-07 13:49:41.716617 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | NaN | 1 | 9 | NaN |
| 9 | 2019-10-09 18:33:55.577963 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | map | 2 | 1 | NaN |
#удалим все пары source-target, шаг которых превышает 3
#и сохраним полученную таблицу в отдельную переменную
df_comp = e[e['step'] <= 3].copy().reset_index(drop=True)
def get_source_index(df):
"""Функция генерации индексов source
Args:
df (pd.DataFrame): исходная таблица с признаками step, source, target.
Returns:
dict: словарь с индексами, именами и соответсвиями индексов именам source.
"""
res_dict = {}
count = 0
# получаем индексы источников
for no, step in enumerate(df['step'].unique().tolist()):
# получаем уникальные наименования для шага
res_dict[no+1] = {}
res_dict[no+1]['sources'] = df[df['step'] == step]['source'].unique().tolist()
res_dict[no+1]['sources_index'] = []
for i in range(len(res_dict[no+1]['sources'])):
res_dict[no+1]['sources_index'].append(count)
count += 1
# соединим списки
for key in res_dict:
res_dict[key]['sources_dict'] = {}
for name, no in zip(res_dict[key]['sources'], res_dict[key]['sources_index']):
res_dict[key]['sources_dict'][name] = no
return res_dict
# создаем словарь
source_indexes = get_source_index(df_comp)
def generate_random_color():
"""Случайная генерация цветов rgba
Args:
Returns:
str: Строка со сгенерированными параметрами цвета
"""
# сгенерим значение для каждого канала
r, g, b = np.random.randint(255, size=3)
return f'rgba({r}, {g}, {b}, 1)'
def colors_for_sources(mode):
"""Генерация цветов rgba
Args:
mode (str): сгенерировать случайные цвета, если 'random', а если 'custom' -
использовать заранее подготовленные
Returns:
dict: словарь с цветами, соответствующими каждому индексу
"""
# словарь, в который сложим цвета в соответствии с индексом
colors_dict = {}
if mode == 'random':
# генерим случайные цвета
for label in df_comp['source'].unique():
r, g, b = np.random.randint(255, size=3)
colors_dict[label] = f'rgba({r}, {g}, {b}, 1)'
elif mode == 'custom':
# присваиваем ранее подготовленные цвета
colors = requests.get('https://raw.githubusercontent.com/rusantsovsv/senkey_tutorial/main/json/colors_senkey.json').json()
for no, label in enumerate(df_comp['source'].unique()):
colors_dict[label] = colors['custom_colors'][no]
return colors_dict
# генерю цвета из своего списка
colors_dict = colors_for_sources(mode='custom')
def percent_users(sources, targets, values):
"""
Расчет уникальных id в процентах (для вывода в hover text каждого узла)
Args:
sources (list): список с индексами source.
targets (list): список с индексами target.
values (list): список с "объемами" потоков.
Returns:
list: список с "объемами" потоков в процентах
"""
# объединим источники и метки и найдем пары
zip_lists = list(zip(sources, targets, values))
new_list = []
# подготовим список словарь с общим объемом трафика в узлах
unique_dict = {}
# проходим по каждому узлу
for source, target, value in zip_lists:
if source not in unique_dict:
# находим все источники и считаем общий трафик
unique_dict[source] = 0
for sr, tg, vl in zip_lists:
if sr == source:
unique_dict[source] += vl
# считаем проценты
for source, target, value in zip_lists:
new_list.append(round(100 * value / unique_dict[source], 1))
return new_list
def lists_for_plot(source_indexes=source_indexes, colors=colors_dict, frac=10):
"""
Создаем необходимые для отрисовки диаграммы переменные списков и возвращаем
их в виде словаря
Args:
source_indexes (dict): словарь с именами и индексами source.
colors (dict): словарь с цветами source.
frac (int): ограничение на минимальный "объем" между узлами.
Returns:
dict: словарь со списками, необходимыми для диаграммы.
"""
sources = []
targets = []
values = []
labels = []
link_color = []
link_text = []
# проходим по каждому шагу
for step in tqdm(sorted(df_comp['step'].unique()), desc='Шаг'):
if step + 1 not in source_indexes:
continue
# получаем индекс источника
temp_dict_source = source_indexes[step]['sources_dict']
# получаем индексы цели
temp_dict_target = source_indexes[step+1]['sources_dict']
# проходим по каждой возможной паре, считаем количество таких пар
for source, index_source in tqdm(temp_dict_source.items()):
for target, index_target in temp_dict_target.items():
# делаем срез данных и считаем количество id
temp_df = df_comp[(df_comp['step'] == step)&(df_comp['source'] == source)&(df_comp['target'] == target)]
value = len(temp_df)
# проверяем минимальный объем потока и добавляем нужные данные
if value > frac:
sources.append(index_source)
targets.append(index_target)
values.append(value)
# делаем поток прозрачным для лучшего отображения
link_color.append(colors[source].replace(', 1)', ', 0.2)'))
labels = []
colors_labels = []
for key in source_indexes:
for name in source_indexes[key]['sources']:
labels.append(name)
colors_labels.append(colors[name])
# посчитаем проценты всех потоков
perc_values = percent_users(sources, targets, values)
# добавим значения процентов для howertext
link_text = []
for perc in perc_values:
link_text.append(f"{perc}%")
# возвратим словарь с вложенными списками
return {'sources': sources,
'targets': targets,
'values': values,
'labels': labels,
'colors_labels': colors_labels,
'link_color': link_color,
'link_text': link_text}
# создаем словарь
data_for_plot = lists_for_plot()
Шаг: 0%| | 0/3 [00:00<?, ?it/s] 0%| | 0/8 [00:00<?, ?it/s] 50%|█████ | 4/8 [00:00<00:00, 35.53it/s] 100%|██████████| 8/8 [00:00<00:00, 34.72it/s] Шаг: 33%|███▎ | 1/3 [00:00<00:00, 4.28it/s] 0%| | 0/10 [00:00<?, ?it/s] 30%|███ | 3/10 [00:00<00:00, 26.94it/s] 100%|██████████| 10/10 [00:00<00:00, 32.02it/s][A Шаг: 100%|██████████| 3/3 [00:00<00:00, 5.43it/s]
def plot_senkey_diagram(data_dict=data_for_plot):
"""
Функция для генерации объекта диаграммы Сенкей
Args:
data_dict (dict): словарь со списками данных для построения.
Returns:
plotly.graph_objs._figure.Figure: объект изображения.
"""
fig = go.Figure(data=[go.Sankey(
domain = dict(
x = [0,1],
y = [0,1]
),
orientation = "h",
valueformat = ".0f",
node = dict(
pad = 50,
thickness = 15,
line = dict(color = "black", width = 0.1),
label = data_dict['labels'],
color = data_dict['colors_labels']
),
link = dict(
source = data_dict['sources'],
target = data_dict['targets'],
value = data_dict['values'],
label = data_dict['link_text'],
color = data_dict['link_color']
))])
fig.update_layout(title_text="Sankey Diagram", font_size=10, width=1000, height=1200)
# возвращаем объект диаграммы
return fig
# сохраняем диаграмму в переменную
senkey_diagram = plot_senkey_diagram()
senkey_diagram.show()
Отлично. Диаграмма построена корректно
</div>
Вывод:
Для построение воронки я возьму путь search- contact_show
Найдем число пользователей, которые совершают событие search и создадим список.
search = session_3.query('event_name =="search"')
count_search= search['user_id'].nunique()
print('Количество пользователей, совершивших событие search =',count_search)
Количество пользователей, совершивших событие search = 1658
list_search= list(search['user_id'])
Найдем количесвто пользователей, которые совершили из события search и событие contact_show
contact_show_ = session_3.query("user_id in @list_search and event_name == 'contacts_show'")
contact_show_count = contact_show_['user_id'].nunique()
print('Количество пользователей, совершивших событие contacts_show=',contact_show_count)
Количество пользователей, совершивших событие contacts_show= 377
from plotly import graph_objects as go
fig = go.Figure(go.Funnel(
y = ['search','contacts_show_'],
x = [search['user_id'].nunique(),contact_show_['user_id'].nunique()],
textposition = "inside",
textinfo = "value+percent initial",
opacity = 0.65, marker = {"color": ["deepskyblue", "lightsalmon", "tan", "teal", "silver"],
"line": {"width": [4, 2, 2, 3, 1, 1], "color": ["wheat", "wheat", "blue", "wheat", "wheat"]}},
connector = {"line": {"color": "royalblue", "dash": "dot", "width": 3}})
)
fig.update_layout(
title="Воронка пути search- contact_show' ",
funnelmode="stack"
)
fig.show()
👍
</div>
По воронке видно, что в итоге 377 пользователей совершили просмотр контактов по цепочке search - contact_show
Для построение воронки я возьму путь map- contact_show
map_1 = session_3.query('event_name =="map"')
count_map= map_1['user_id'].nunique()
print('Количество пользователей, совершивших событие map =',count_map)
Количество пользователей, совершивших событие map = 1440
list_map_1= list(map_1['user_id'])
contact_show = session_3.query("user_id in @list_map_1 and event_name == 'contacts_show'")
contact_show_count_1 = contact_show['user_id'].nunique()
print('Количество пользователей, совершивших событие contacts_show=',contact_show_count_1)
Количество пользователей, совершивших событие contacts_show= 289
from plotly import graph_objects as go
fig = go.Figure(go.Funnel(
y = ['map_1','contacts_show'],
x = [map_1['user_id'].nunique(),contact_show['user_id'].nunique()],
textposition = "inside",
textinfo = "value+percent initial",
opacity = 0.65, marker = {"color": ["deepskyblue", "lightsalmon", "tan", "teal", "silver"],
"line": {"width": [4, 2, 2, 3, 1, 1], "color": ["wheat", "wheat", "blue", "wheat", "wheat"]}},
connector = {"line": {"color": "royalblue", "dash": "dot", "width": 3}})
)
fig.update_layout(
title="Воронка пути map - contact_show' ",
funnelmode="stack"
)
fig.show()
По воронке видно, что 289 пользователей совершили просмотр контактов по цепочке map - contact_show
Для построение воронки я возьму путь tips_show - contact_show
tips_show = session_3.query('event_name =="tips_show"')
count_tips_show= tips_show['user_id'].nunique()
print('Количество пользователей, совершивших событие tips_show =',count_tips_show)
Количество пользователей, совершивших событие tips_show = 2771
list_tips_show= list(tips_show['user_id'])
contact_show = session_3.query("user_id in @list_tips_show and event_name == 'contacts_show'")
contact_show_count_3 = contact_show['user_id'].nunique()
print('Количество пользователей, совершивших событие contacts_show=',contact_show_count_3)
Количество пользователей, совершивших событие contacts_show= 516
from plotly import graph_objects as go
fig = go.Figure(go.Funnel(
y = ['tips_show','contact_show'],
x = [tips_show['user_id'].nunique(),
contact_show['user_id'].nunique()],
textposition = "inside",
textinfo = "value+percent initial",
opacity = 0.65, marker = {"color": ["deepskyblue", "lightsalmon", "tan", "teal", "silver"],
"line": {"width": [4, 2, 2, 3, 1, 1], "color": ["wheat", "wheat", "blue", "wheat", "wheat"]}},
connector = {"line": {"color": "royalblue", "dash": "dot", "width": 3}})
)
fig.update_layout(
title="Воронка пути tips_show - contact_show ",
funnelmode="stack"
)
fig.show()
По воронке видно, что 516 пользователей совершили просмотр контактов по цепочке tips_show - contact_show
Для построение воронки я возьму путь map-tips_show - contact_show
map_1 = session_3.query('event_name =="map"')
count_map= map_1['user_id'].nunique()
print('Количество пользователей, совершивших событие map =',count_map)
Количество пользователей, совершивших событие map = 1440
list_map_1= list(map_1['user_id'])
tips_show=session_3.query("user_id in @list_map_1 and event_name == 'tips_show'")
list_tips_show = list(tips_show['user_id'])
count_tips_show= tips_show['user_id'].nunique()
print('Количество пользователей, совершивших событие tips_show =',count_tips_show)
Количество пользователей, совершивших событие tips_show = 1352
contact_show = session_3.query("user_id in @list_tips_show and event_name == 'contacts_show'")
contact_show_count_2 = contact_show['user_id'].nunique()
print('Количество пользователей, совершивших событие contacts_show=',contact_show_count_2)
Количество пользователей, совершивших событие contacts_show= 275
from plotly import graph_objects as go
fig = go.Figure(go.Funnel(
y = ['map_1','tips_show','contact_show'],
x = [map_1['user_id'].nunique(),
tips_show['user_id'].nunique(),
contact_show['user_id'].nunique()],
textposition = "inside",
textinfo = "value+percent initial",
opacity = 0.65, marker = {"color": ["deepskyblue", "lightsalmon", "tan", "teal", "silver"],
"line": {"width": [4, 2, 2, 3, 1, 1], "color": ["wheat", "wheat", "blue", "wheat", "wheat"]}},
connector = {"line": {"color": "royalblue", "dash": "dot", "width": 3}})
)
fig.update_layout(
title="Воронка пути map - tips_show - contact_show ",
funnelmode="stack"
)
fig.show()
Вывод: Воронка пути search- contact_show' показала самый лучший результат. Всего 23% пользователя от поиска перешли на просмотр контактов(377 человек).
Одни пользователи совершают действия tips_show и tips_click, другие — тольĸо tips_show. Проверьте гипотезу: ĸонверсия в просмотры ĸонтаĸтов различается у этих двух групп.
Нулевая гипотеза (H0) - различия ĸонверсий в просмотры ĸонтаĸтов между группами нет
Альтернативная гипотеза (H1)- различия ĸонверсий в просмотры ĸонтаĸтов между группами есть
Очень хорошо, что не забываешь про формулировку гипотез. Это важный пункт в подобного рода задачах.
</div>
Чтобы рассчитать статистическую значимость различий конверсий, сравним генеральные совокупности: ĸонверсию tips_show и tips_click в просмотры ĸонтаĸтов и тольĸо tips_show в просмотры ĸонтаĸтов.
Выводим пользователей, которые совершили просмотр контактов и создадим лист
users_show = session_3.pivot_table(index=['user_id','event_name'], values='event_date_time',aggfunc='first').reset_index()
list_users_show = list(users_show .query('event_name == "contacts_show"')['user_id'])
users_show
| user_id | event_name | event_date_time | |
|---|---|---|---|
| 0 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | map | 2019-10-09 18:33:55.577963 |
| 1 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | tips_show | 2019-10-07 13:39:45.989359 |
| 2 | 00157779-810c-4498-9e05-a1e9e3cedf93 | advert_open | 2019-10-24 10:52:18.644065 |
| 3 | 00157779-810c-4498-9e05-a1e9e3cedf93 | contacts_call | 2019-10-20 19:17:24.887762 |
| 4 | 00157779-810c-4498-9e05-a1e9e3cedf93 | contacts_show | 2019-10-20 19:17:18.659799 |
| ... | ... | ... | ... |
| 9566 | ffe68f10-e48e-470e-be9b-eeb93128ff1a | photos_show | 2019-10-21 16:49:57.103308 |
| 9567 | ffe68f10-e48e-470e-be9b-eeb93128ff1a | search | 2019-10-21 16:39:33.867145 |
| 9568 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | contacts_show | 2019-10-16 12:57:40.598641 |
| 9569 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | map | 2019-10-12 01:07:53.959366 |
| 9570 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | tips_show | 2019-10-12 00:57:21.241896 |
9571 rows × 3 columns
users_show_count = users_show['user_id'].nunique()
print('Количество пользователей,которые совершили просмотр контактов =',users_show_count)
Количество пользователей,которые совершили просмотр контактов = 4228
user_show_tips = users_show.query('user_id in @list_users_show and event_name == "tips_show"')['user_id'].nunique()
print('Количество пользователей(просматривали контакты),которые совершили tips_show = ', user_show_tips)
Количество пользователей,которые совершили tips_show = 516
Создадим лист, где указано Количество пользователей,которые совершили tips_show
list_user_show_tips = list(users_show.query('user_id in @list_users_show and \
event_name == "tips_show"')['user_id'])
user_show_tips_and_click = users_show.query('user_id in @list_user_show_tips and \
event_name == "tips_click"')['user_id'].nunique()
print('Количество пользователей, ĸто совершил tips_show так же совершил событие tips_click=', user_show_tips_and_click)
Количество пользователей, ĸто совершил tips_show так же совершил событие tips_click= 91
tips_show_count = users_show.query('event_name == "tips_show"')['user_id'].nunique()
print('Kоличество пользователей,ĸоторые совершили tips_show =',tips_show_count)
Kоличество пользователей,ĸоторые совершили tips_show = 2771
list_tips_show_count = list(users_show.query('event_name == "tips_show"')['user_id'])
tips_click_count = users_show.query('user_id in @list_users_show and \
event_name == "tips_click"')['user_id'].nunique()
print('Kоличество пользователей,ĸоторые совершили tips_click:', tips_click_count)
Kоличество пользователей,ĸоторые совершили tips_click: 100
Посчитаем количество пользователей,кто совершил tips_show + tips_click
count_tips_show_and_tips_click= users_show.query('user_id in @list_tips_show_count and event_name == "tips_click"')['user_id'].nunique()
print('Количество пользователей,кто совершил tips_show + tips_click=',count_tips_show_and_tips_click)
Количество пользователей,кто совершил tips_show + tips_click= 297
Количество пользователей, которые совершили tips_show, которые так же просматривали контакты отнимем количество пользователей,ĸоторые совершили tips_click
user_show_tips_no_tips_click = user_show_tips - tips_click_count
print('Количество пользователей, которые совершили tips_show, которые так же просматривали контакты =',user_show_tips_no_tips_click)
Количество пользователей, которые совершили tips_show, которые так же просматривали контакты = 416
only_tips_show =tips_show_count-count_tips_show_and_tips_click
print('Количество пользователей, которые совершили tips_show=',only_tips_show)
Количество пользователей, которые совершили tips_show= 2474
Критический уровень статистической значимости alpha = 0.05
purchases = np.array([user_show_tips_and_click, count_tips_show_and_tips_click])
leads = np.array([user_show_tips_no_tips_click, only_tips_show])
alpha = 0.05
purchases = np.array([user_show_tips_and_click, count_tips_show_and_tips_click])
leads = np.array([user_show_tips_no_tips_click, only_tips_show])
print(purchases, leads)
# пропорция успехов в первой группе:
p1 = purchases[0]/leads[0]
# пропорция успехов во второй группе:
p2 = purchases[1]/leads[1]
# пропорция успехов в комбинированном датасете:
p_combined = (purchases[0] + purchases[1]) / (leads[0] + leads[1])
# разница пропорций в датасетах
difference = p1 - p2
# считаем статистику в ст.отклонениях стандартного нормального распределения
z_value = difference / mth.sqrt(p_combined * (1 - p_combined) * (1/leads[0] + 1/leads[1]))
# задаем стандартное нормальное распределение (среднее 0, ст.отклонение 1)
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value))) * 2
print('p-значение: ', p_value)
if p_value < alpha:
print('Отвергаем нулевую гипотезу: между значениями есть значимая разница')
else:
print(
'Не получилось отвергнуть нулевую гипотезу, нет оснований считать значения разными'
)
[ 91 297] [ 416 2474] p-значение: 4.672112763337566e-08 Отвергаем нулевую гипотезу: между значениями есть значимая разница
Методологически проверка гипотез проведена верно.
Для информативности, можно было добавить показатели конверсии данных групп.
</div>
Делаем вывод, что конверсия просмотров ĸонтаĸтов различается у групп, а именно, что: Одни пользователи совершают действия tips_show и tips_click(увидел рекомендованное объявление и кликнул, другие — тольĸо tips_show(только кликнул по рекомендованному объявлению).
Одни пользователи добавляют объявление в избранное, только, после открытия карточки advert_open, другие - без открытия карточки. Проверьте гипотезу: конверсия в просмотры контактов различаются у этих двух групп.
Нулевая гипотеза (H0) - различия ĸонверсий в просмотры ĸонтаĸтов между группами нет
Альтернативная гипотеза (H1)- различия ĸонверсий в просмотры ĸонтаĸтов между группами есть
Чтобы рассчитать статистическую значимость различий конверсий, сравним генеральные совокупности: ĸонверсию advert_open и favorites_add в просмотры ĸонтаĸтов и тольĸо в favorites_add просмотры ĸонтаĸтов.
Выводим пользователей, которые совершили просмотр контактов
print('Количество пользователей,которые совершили просмотр контактов =',users_show_count)
Количество пользователей,которые совершили просмотр контактов = 4228
Посчитаем кто из этих пользователей (users_show) совершил favorites_add
user_show_favorites_add = users_show.query('user_id in @list_users_show and \
event_name == "favorites_add"')['user_id'].nunique()
print('Кол-во пользователей, совершивших favorites_add:', user_show_favorites_add)
Кол-во пользователей, совершивших favorites_add: 136
Создадим лист, где указано Количество пользователей,которые совершили favorites_add
list_user_show_favorites_add = list(users_show.query('user_id in @list_users_show and \
event_name == "favorites_add"')['user_id'])
Вычислим количество пользователей, из тех, ĸто совершили favorites_add так же совершили событие advert_open
favorites_add_advert_open_count = users_show.query('user_id in @list_user_show_favorites_add and \
event_name == "advert_open"')['user_id'].nunique()
print('Количество пользователей, совершивших avorites_add и advert_open=', favorites_add_advert_open_count)
Количество пользователей, совершивших avorites_add и advert_open= 58
Вычислим ĸол-во пользователей, ĸто совершил favorites_add и advert_open + favorites_add
total_favorit_and_open = users_show.query('event_name == "favorites_add"')['user_id'].nunique()
total_favorit_and_open
351
list_total_favorit_and_open = list(users_show.query('event_name == "favorites_add"')['user_id'])
Посчитаем кто из этих пользователей (users_show) совершил advert_open
user_show_advert_open = users_show.query('user_id in @list_users_show and event_name == "advert_open"')['user_id'].nunique()
print('Количество пользователей,которые совершили advert_open = ', user_show_advert_open)
Количество пользователей,которые совершили advert_open = 138
Количество пользователей, совершивших favorites_add и advert_open
count_favorites_add_and_advert_open = users_show.query('user_id in @list_total_favorit_and_open and event_name == "advert_open"')['user_id'].nunique()
count_favorites_add_and_advert_open
120
Вычтем из user_show_advert_open - user_show_favorites_add
difference = user_show_advert_open-user_show_favorites_add
difference
2
dif = total_favorit_and_open - count_favorites_add_and_advert_open
print('Количество пользователей, совершивших favorites_add всего=',dif)
Количество пользователей, совершивших favorites_add всего= 231
alpha = 0.05 # критический уровень статистической значимости
purchases = np.array([favorites_add_advert_open_count, count_favorites_add_and_advert_open])
leads = np.array([difference, dif])
# пропорция успехов в первой группе:
p1 = purchases[0]/leads[0]
# пропорция успехов во второй группе:
p2 = purchases[1]/leads[1]
# пропорция успехов в комбинированном датасете:
p_combined = (purchases[0] + purchases[1]) / (leads[0] + leads[1])
# разница пропорций в датасетах
difference = p1 - p2
# считаем статистику в ст.отклонениях стандартного нормального распределения
z_value = difference / mth.sqrt(p_combined * (1 - p_combined) * (1/leads[0] + 1/leads[1]))
# задаем стандартное нормальное распределение (среднее 0, ст.отклонение 1)
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value))) * 2
print('p-значение: ', p_value)
if p_value < alpha:
print('Отвергаем нулевую гипотезу: между долями есть значимая разница')
else:
print(
'Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными'
)
p-значение: 0.0 Отвергаем нулевую гипотезу: между долями есть значимая разница
И здесь расчет корректный
</div>
Вывод:
У нас имелись два датафрейма. Первый - mobile_df - содержит данные о идентификаторах пользователей, времени совершения событий и типе событий. Второй - source_df - также содержит данные об идентификаторах пользователей и об источниках, с которых пользователи устанавливают приложение.
Проверили данные на пропуски и дубликаты, а так же привели все данные к нужному типу. Данные мы использовали в период с 7 октября по 3 ноября. 28 дней. в объеденном датафрейме у нас получилось:
Количество событий - 74197 Количество пользователей - 4293 Количество пользователей, совершивших ЦС( просмотр контактов) -981 В среднем на пользователя приходится - 17 событий конверсию в целевое действие(просмотр контактов) = 23%
Самым частымсобытием является tips_show (пользователь увидел рекомендованные объявления) - я думаю, что рекомендованные объявления находятся на главной странице. Далее идет событие photos_show (просмотр фотографий в объявлении). Самым не популярное событие search(1-7), либо пользователи не пользуются поиском, так как либо его не видят, либо подстроено так, что приложение считывает пользователей, по собранным куки и выдает на главной странице то, что пользователь недавно искал в поисковой странице.
Больше всего пользователей в приложение приходит из источниĸа yandex , и совершают больше всего просмотров ĸонтаĸтов. Но показатль пользователей в просмотры ĸонтаĸтов ниже, чем у других источниĸов. У other показатль пользователей в просмотры ĸонтаĸтов выше, чем у yandex и google. Самая высоĸая ĸонверсия в просмотр ĸонтаĸтов у события favorites_add.
Проверили гипотезы:
Одни пользователи совершают действия tips_show и tips_click, другие — тольĸо tips_show. Проверьте гипотезу: ĸонверсия в просмотры ĸонтаĸтов различается у этих двух групп. Ответ : между значениями есть значимая разница.
Проверили гипотезы: Одни пользователи добавляют объявление в избранное, только, после открытия карточки advert_open, другие - без открытия карточки. Проверьте гипотезу: конверсия в просмотры контактов различаются у этих двух групп.
Ответ : между значениями есть значимая разница.
Рекомендации:
Пользователь видит рекомендованные объявления, но не перходит на них. Может стоить улучшить, делав подборку объявлений для пользователей.
Если пользователь долго не заходит в приложение, или не добился конечной цели, не посмотрел номер контакта, может следует напоминать о приложении, а так же оповещать пользователя, если появляются новые объявления с историей его просмотров.
👍
</div>